package com.brillilab.service.logic.experiments;

import com.brillilab.common.utils.MyBeanUtils;
import com.brillilab.domain.enums.common.BoolEnum;
import com.brillilab.domain.enums.experiment.ExperimentStepTimerStateEnum;
import com.brillilab.domain.enums.lab.UserEnum;
import com.brillilab.domain.po.experiments.Experiment;
import com.brillilab.domain.po.experiments.*;
import com.brillilab.domain.po.lab.LabMember;
import com.brillilab.domain.po.user.Users;
import com.brillilab.domain.vo.experiments.ExperimentStepTimerVo;
import com.brillilab.domain.vo.user.UserInfoVo;
import com.brillilab.service.constant.JobConstants;
import com.brillilab.service.core.experiments.*;
import com.brillilab.service.core.lab.ILabMemberService;
import com.brillilab.service.core.user.IUsersService;
import com.brillilab.service.job.experiment.TimerEndJob;
import com.brillilab.service.quartz.BrillQuartzScheduler;
import com.brillilab.service.utils.QuartzUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 实验步骤计时器
 * @author WuMenghao
 * @time   2019/04/23
 */
@Service
@Transactional
public class ExperimentStepTimerLogic {

    @Resource
    private IExperimentStepTimerService experimentStepTimerService;
    @Resource
    private IExperimentStepService experimentStepService;
    @Resource
    private BrillQuartzScheduler scheduler;
    @Resource
    private IPlateGridTimerService plateGridTimerService;
    @Resource
    private IPlateGridService plateGridService;
    @Resource
    private IPlateGridReagentService plateGridReagentService;
    @Resource
    private IExperimentService experimentService;
    @Resource
    private IExperimentPhaseService experimentPhaseService;
    @Resource
    private ILabMemberService labMemberService;
    @Resource
    private IUsersService usersService;
    @Resource
    private IPlateGridTextService plateGridTextService;

    /**
     * 添加计时器
     * @param experimentId
     * @param phaseId
     * @param stepId
     * @param time
     * @return
     */
    public ExperimentStepTimerVo addTimerToStep(Long experimentId,Long phaseId,Long stepId,Long time) {

        //插入数据
        ExperimentStepTimer timer=new ExperimentStepTimer();
        timer.setExperimentId(experimentId);
        timer.setPhaseId(phaseId);
        timer.setStepId(stepId);
        timer.setTime(time);
        timer.setRemainTime(time);
        timer.setState(ExperimentStepTimerStateEnum.NOT_START.getState());
        Assert.isTrue(experimentStepTimerService.insert(timer),"业务操作失败！");

        return MyBeanUtils.copyBean(timer,ExperimentStepTimerVo.class);
    }

    /**
     * 添加计时器
     * @param experimentId
     * @param phaseId
     * @param stepId
     * @param timeList
     * @return
     */
    public ExperimentStepTimerVo addTimerToStepBatch(Long experimentId,Long phaseId,Long stepId,List<Long> timeList) {

        //插入数据
        List<ExperimentStepTimer> timers=timeList.stream().map(time -> {
            ExperimentStepTimer timer=new ExperimentStepTimer();
            timer.setExperimentId(experimentId);
            timer.setPhaseId(phaseId);
            timer.setStepId(stepId);
            timer.setTime(time);
            timer.setRemainTime(time);
            timer.setState(ExperimentStepTimerStateEnum.NOT_START.getState());
            return timer;
        }).collect(Collectors.toList());
        Assert.isTrue(experimentStepTimerService.insertBatch(timers),"业务操作失败！");

        if(timers.size() == 1){
            return MyBeanUtils.copyBean(timers.get(0),ExperimentStepTimerVo.class);
        }

        return null;
    }

    private void createTimerJob(Date endTime,ExperimentStepTimer timer,Long labId,Experiment experiment,ExperimentPhase phase,ExperimentStep step) {

        LabMember labMember=labMemberService.selectById(experiment.getExecutorId());
        Users users=usersService.selectUser(labMember.getUserId(),UserEnum.State.NORMAL.getValue());
        long now=new Date().getTime();
        //设置定时任务 更改状态 结束-2
        String cronExpr=QuartzUtils.getCronExpr(endTime);
        HashMap<String, Object> data=new HashMap<>();
        data.put("timerId",timer.getId());
        data.put("labId",labId);
        data.put("executorUid",users.getId());
        data.put("executor",labMember.getId());
        data.put("experimentName",experiment.getName());
        data.put("stepNo",phase.getSort() + "." +step.getSort());
        data.put("experimentId",experiment.getId());
        data.put("phaseId",phase.getId());
        data.put("stepId",step.getId());
        scheduler.createJob(TimerEndJob.class,JobConstants.JOB_GROUP_TIMER_END+now,JobConstants.JOB_GROUP_TIMER_END,cronExpr,data);

        timer.setJobGroup(JobConstants.JOB_GROUP_TIMER_END);
        timer.setJobName(JobConstants.JOB_GROUP_TIMER_END+now);
    }

    /**
     * 暂停
     * @param experimentId
     * @param phaseId
     * @param stepId
     * @param labId
     * @param labMemberId
     * @return
     */
    public boolean pauseStepTimers(Long experimentId,Long phaseId,Long stepId,Long labId,Long labMemberId) {

        experimentStepService.getExperimentStepAndAssertExist(experimentId,phaseId,stepId,labId,labMemberId);

        List<ExperimentStepTimer> timers=experimentStepTimerService.selectList(experimentId,phaseId,stepId)
                .stream().filter(e -> e.getState().equals(ExperimentStepTimerStateEnum.START.getState())).collect(Collectors.toList());

        for (ExperimentStepTimer timer:timers){
            //更新状态
            Date nextFireTime=scheduler.getNextFireTime(timer.getJobName(),timer.getJobGroup());
            long remainTime ;
            if(nextFireTime!=null){
                long c=System.currentTimeMillis();
                remainTime = nextFireTime.getTime() - c;
            }else {
                remainTime = 0;
            }
            timer.setRemainTime(remainTime >1000 ? remainTime : 0);
            timer.setState(remainTime >1000 ? ExperimentStepTimerStateEnum.PAUSED.getState() : ExperimentStepTimerStateEnum.END.getState());
            timer.setUpdateTime(new Date());

            //删除定时任务
            if(nextFireTime!=null){
                scheduler.deleteJob(timer.getJobName(),timer.getJobGroup());
            }
        }

        return experimentStepTimerService.updateBatch(timers);
    }

    /**
     * 开始
     * @param experimentId
     * @param phaseId
     * @param stepId
     * @param labId
     * @param labMemberId
     * @return
     */
    public boolean startStepTimers(Long experimentId,Long phaseId,Long stepId,Long labId,Long labMemberId) {

        Experiment experiment=experimentService.selectById(experimentId);
        Assert.notNull(experiment,"实验不存在！");
        Assert.isTrue(experiment.getLabId().equals(labId),"实验不属于该实验室！");
        Assert.isTrue(experiment.getExecutorId().equals(labMemberId),"您不是该实验的执行人，无法进行此操作！");

        ExperimentPhase phase=experimentPhaseService.selectById(phaseId);
        Assert.notNull(phase,"实验阶段不存在！");
        Assert.isTrue(phase.getExperimentId().equals(experimentId) ,"该阶段不属于本实验！");

        ExperimentStep step=experimentStepService.selectById(stepId);
        Assert.notNull(step,"实验步骤不存在！");
        Assert.isTrue(step.getExperimentId().equals(experimentId) ,"该步骤不属于本实验！");
        Assert.isTrue(step.getExperimentPhaseId().equals(phaseId) ,"该步骤不属于本阶段！");

        List<ExperimentStepTimer> timers=experimentStepTimerService.selectList(experimentId,phaseId,stepId)
                .stream().filter(e -> e.getState().equals(ExperimentStepTimerStateEnum.NOT_START.getState())
                        || e.getState().equals(ExperimentStepTimerStateEnum.PAUSED.getState())).collect(Collectors.toList());


        for (ExperimentStepTimer timer:timers){
            if(timer.getState().equals(ExperimentStepTimerStateEnum.END.getState())){
                continue;
            }
            //结束时间
            Long endTimestamp = new Date().getTime()+timer.getRemainTime();
            Date endTime = new Date(endTimestamp);
            //更新状态
            timer.setEndTime(endTime);
            timer.setState(ExperimentStepTimerStateEnum.START.getState());
            timer.setUpdateTime(new Date());

            //新建定时任务
            createTimerJob(endTime,timer,labId,experiment,phase,step);
        }

        experimentStepService.startStep(stepId);

        return experimentStepTimerService.updateBatch(timers);
    }

    /**
     * 重置
     * @param experimentId
     * @param phaseId
     * @param stepId
     * @param labId
     * @param labMemberId
     * @return
     */
    public boolean resetStepTimers(Long experimentId,Long phaseId,Long stepId,Long labId,Long labMemberId) {

        experimentStepService.getExperimentStepAndAssertExist(experimentId,phaseId,stepId,labId,labMemberId);

        List<ExperimentStepTimer> timers=experimentStepTimerService.selectList(experimentId,phaseId,stepId);

        for (ExperimentStepTimer timer:timers){
            //删除定时任务
            if(!StringUtils.isEmpty(timer.getJobName()) && !StringUtils.isEmpty(timer.getJobGroup()) ){
                scheduler.deleteJob(timer.getJobName(),timer.getJobGroup());
            }
            //重置
            timer.setTime(timer.getTime());
            timer.setRemainTime(timer.getTime());
            timer.setState(ExperimentStepTimerStateEnum.NOT_START.getState());
            timer.setEndTime(null);
            timer.setJobName(null);
            timer.setJobGroup(null);
            timer.setUpdateTime(new Date());
        }

        return experimentStepTimerService.updateBatch(timers);
    }

    /**
     * 实验步骤中计时器删除
     * @param experimentId
     * @param phaseId
     * @param stepId
     * @param stepTimerId
     * @param userInfoVo
     */
    public void timerDelete(Long experimentId, Long phaseId, Long stepId, Long stepTimerId, UserInfoVo userInfoVo) {
        //校验
        experimentStepService.getExperimentStepAndAssertExist(experimentId, phaseId, stepId, userInfoVo.getLabId(), userInfoVo.getLabMemberId());
        //删除定时任务
        ExperimentStepTimer experimentStepTimer = experimentStepTimerService.selectById(stepTimerId);
        Assert.notNull(experimentStepTimer,"实验步骤计时器不存在！");

        if (experimentStepTimer.getState().equals(ExperimentStepTimerStateEnum.START.getState())) {//计时器状态为开始
            //停止计时
            scheduler.deleteJob(experimentStepTimer.getJobName(), experimentStepTimer.getJobGroup());
        }
        //删除stepTimer
        experimentStepTimerService.deleteById(stepTimerId);
        //gridIds
        List<PlateGridTimer> gridTimers = plateGridTimerService.selectListByStepTimerId(stepTimerId);
        List<Long> gridIds=gridTimers.stream().map(PlateGridTimer::getPlateGridId).distinct().collect(Collectors.toList());
        //删除gridTimer
        plateGridTimerService.deleteByStepTimerId(stepTimerId);
        //isUsed
        setGridNotUseForTime(gridIds);
    }

    public void setGridNotUseForTime(List<Long> gridIds) {
        List<PlateGrid> plateGrids=plateGridService.selectList(gridIds);

        //所有GridTimer
        List<PlateGridTimer> timers = plateGridTimerService.selectList(gridIds);
        List<PlateGridReagent> reagents = plateGridReagentService.selectList(gridIds);
        List<PlateGridText> texts = plateGridTextService.selectList(gridIds);

        List<PlateGrid> update = new ArrayList<>();
        gridIds.forEach(gridId -> {
            long rct = timers.stream().filter(e -> e.getPlateGridId().equals(gridId)).count();
            long tmct =reagents.stream().filter(e -> e.getPlateGridId().equals(gridId)).count();
            long tct = texts.stream().filter(e -> e.getPlateGridId().equals(gridId)).count();

            if(rct == 0 && tmct == 0 && tct == 0){
                PlateGrid grid=plateGrids.stream().filter(e ->e.getId().equals(gridId)).findFirst().orElse(null);
                if(grid!=null){
                    grid.setIsUsed(BoolEnum.FALSE.getValue());
                    update.add(grid);
                }
            }
        });

        plateGridService.updateBatch(update);
    }
}
